/*
	----------------------------------------------------------
	MIDI.Player : 0.3.1 : 2015-03-26
	----------------------------------------------------------
	https://github.com/mudcube/MIDI.js
	----------------------------------------------------------
*/

if (typeof MIDI === 'undefined') MIDI = {};
if (typeof MIDI.Player === 'undefined') MIDI.Player = {};

(function () {
	'use strict';

	var midi = MIDI.Player;
	midi.currentTime = 0;
	midi.endTime = 0;
	midi.restart = 0;
	midi.playing = false;
	midi.timeWarp = 1;
	midi.startDelay = 0;
	midi.BPM = 120;
	//
	midi.ticksPerBeat = 480;
	midi.currentTicks = 0;
	midi.endTicks = 0;
	midi.constVelocity = 88;
	midi.currentIndex = -1; //Having playing last note Index 
	midi.start =
		midi.resume = function (onsuccess) {
			// if (midi.currentTime < -1) {
			// 	midi.currentTime = -1;
			// }
			// startAudio(midi.currentTime, null, onsuccess);

			if (midi.currentTicks < 0) {
				midi.currentTicks = 0;
			}
			console.log("In resume",midi.currentTicks);
			startAudioNew(midi.currentTicks, null, onsuccess);
		};

	midi.pause = function () {
		console.log("pause");
		var tmp = midi.restart;
		stopAudio();
		midi.restart = tmp;
	};

	midi.stop = function () {
		stopAudio();
		midi.restart = -1;
		midi.currentTime = 0;
		midi.currentTicks = 0;
		midi.currentIndex = -1;
	};

	midi.addListener = function (onsuccess) {
		onMidiEvent = onsuccess;
	};

	midi.removeListener = function () {
		onMidiEvent = undefined;
	};

	midi.clearAnimation = function () {
		if (midi.animationFrameId) {
			cancelAnimationFrame(midi.animationFrameId);
		}
	};
	midi.setAnimation = function (callback) {
		var currentTime = 0;
		var tOurTime = 0;
		var tTheirTime = 0;
		//
		midi.clearAnimation();
		///
		var frame = function () {
			midi.animationFrameId = requestAnimationFrame(frame);
			///
			if (midi.endTime === 0) {
				return;
			}
			if (midi.playing) {
				currentTime = (tTheirTime === midi.currentTime) ? tOurTime - Date.now() : 0;
				if (midi.currentTime === 0) {
					currentTime = 0;
				} else {
					currentTime = midi.currentTime - currentTime;
				}
				if (tTheirTime !== midi.currentTime) {
					tOurTime = Date.now();
					tTheirTime = midi.currentTime;
				}
			} else { // paused
				currentTime = midi.currentTime;
			}
			///
			var endTime = midi.endTime;
			var percent = currentTime / endTime;
			var total = currentTime / 1000;
			var minutes = total / 60;
			var seconds = total - (minutes * 60);
			var t1 = minutes * 60 + seconds;
			var t2 = (endTime / 1000);
			///
			if (t2 - t1 < -1.0) {
				return;
			} else {
				callback({
					now: t1,
					end: t2,
					events: noteRegistrar
				});
			}
		};
		///
		requestAnimationFrame(frame);
	};
	midi.summaryShift = function (mode) {
		var playing = midi.playing;
		var offset = 0;
		midi.pause();
		var sumN = Math.floor(midi.currentTicks / (midi.ticksPerBeat * 4));
		if (mode) {
			if(sumN )	sumN -= 1;
		} else sumN += 1;
		midi.currentTicks = sumN * midi.ticksPerBeat * 4;
		if(midi.currentTicks > midi.endTicks)
			midi.currentTicks = midi.endTicks;
		midi.currentIndex = -1;
		while(offset < midi.currentTicks){
			offset += midi.data[++midi.currentIndex][0].ticksToEvent;
		}
		if(midi.currentIndex+1)  midi.currentIndex--;
		// debug
		// console.log(midi.currentTicks);
		if (playing) {
			midi.resume();
		}
		//---------------------------------/////
		// var playing = midi.playing;
		// var offset = 0;
		// midi.pause();
		// var sumN = Math.floor(midi.currentTicks / (midi.ticksPerBeat * 4));
		// var remainer = midi.currentTicks % (midi.ticksPerBeat * 4);
		// if (mode) {
		// 	if(sumN && remainer < 2*midi.ticksPerBeat)	sumN -= 1;
		// } else sumN += 1;
		// midi.currentTicks = sumN * midi.ticksPerBeat * 4;
		// if(midi.currentTicks > midi.endTicks)
		// 	midi.currentTicks = midi.endTicks;
		// midi.currentIndex = -1;
		// while(offset < midi.currentTicks){
		// 	offset += midi.data[++midi.currentIndex][0].ticksToEvent;
		// }
		// if(midi.currentIndex+1)  midi.currentIndex--;
		// // debug
		// // console.log(midi.currentTicks);
		// if (playing) {
		// 	midi.resume();
		// }
	}


	// helpers

	//test change the velocity to constVelocity
	midi.changeVelocity = function () {
		//the number of tracks
		for (var trackn = 0; trackn < midi.currentData.tracks.length; trackn++) {
			// the number of event in current tracks
			for (var n = 0; n < midi.currentData.tracks[trackn].length; n++) {
				// is there attribution : velocity?
				if (midi.currentData.tracks[trackn][n].velocity) {	//Change the velocity
					midi.currentData.tracks[trackn][n].velocity = midi.constVelocity;
				}
			}
		}
	}
	midi.moveCursor = function(currentTicks){
		var mvTicks = currentTicks%(480*4*4);
		$('.point').css('left', mvTicks/120*20+100+'px');
	}
	midi.loadMidiFile = function (onsuccess, onprogress, onerror) {
		try {
			// midi.replayer = new Replayer(MidiFile(midi.currentData), midi.timeWarp, null, midi.BPM);
			midi.currentData = MidiFile(midi.currentData);
			// midi.changeVelocity();
			midi.replayer = new Replayer(midi.currentData, midi.timeWarp, null, midi.BPM);
			midi.data = midi.replayer.getData();
			midi.endTime = getLength();
			midi.endTicks = getLengthNew();
			//debug
			console.log("midiFile:", midi.currentData);
			console.log("midiData:", midi.data);
			///
			MIDI.loadPlugin({
				// 			instruments: midi.getFileInstruments(),
				onsuccess: onsuccess,
				onprogress: onprogress,
				onerror: onerror
			});
		} catch (event) {
			onerror && onerror(event);
		}
	};

	midi.loadFile = function (file, onsuccess, onprogress, onerror) {
		midi.stop();
		if (file.indexOf('base64,') !== -1) {
			var data = window.atob(file.split(',')[1]);
			midi.currentData = data;
			midi.loadMidiFile(onsuccess, onprogress, onerror);
		} else {
			var fetch = new XMLHttpRequest();
			fetch.open('GET', file);
			fetch.overrideMimeType('text/plain; charset=x-user-defined');
			fetch.onreadystatechange = function () {
				if (this.readyState === 4) {
					if (this.status === 200) {
						var t = this.responseText || '';
						var ff = [];
						var mx = t.length;
						var scc = String.fromCharCode;
						for (var z = 0; z < mx; z++) {
							ff[z] = scc(t.charCodeAt(z) & 255);
						}
						///
						var data = ff.join('');
						midi.currentData = data;
						midi.loadMidiFile(onsuccess, onprogress, onerror);
					} else {
						onerror && onerror('Unable to load MIDI file');
					}
				}
			};
			fetch.send();
		}
	};

	midi.getFileInstruments = function () {
		var instruments = {};
		var programs = {};
		for (var n = 0; n < midi.data.length; n++) {
			var event = midi.data[n][0].event;
			if (event.type !== 'channel') {
				continue;
			}
			var channel = event.channel;
			switch (event.subtype) {
				case 'controller':
					//				console.log(event.channel, MIDI.defineControl[event.controllerType], event.value);
					break;
				case 'programChange':
					programs[channel] = event.programNumber;
					break;
				case 'noteOn':
					var program = programs[channel];
					var gm = MIDI.GM.byId[isFinite(program) ? program : channel];
					instruments[gm.id] = true;
					break;
			}
		}
		var ret = [];
		for (var key in instruments) {
			ret.push(key);
		}
		return ret;
	};

	// Playing the audio

	var eventQueue = []; // hold events to be triggered
	var queuedTime; // 
	var queuedTicks;
	var startTime = 0; // to measure time elapse
	var noteRegistrar = {}; // get event for requested note
	var onMidiEvent = undefined; // listener
	var scheduleTracking = function (channel, note, currentTicks, offset, message, velocity, event, time) {
		return setTimeout(function () {
			var data = {
				channel: channel,
				note: note,
				now: currentTicks,
				end: midi.endTicks,
				message: message,
				velocity: velocity,
				event: event
			};
			//
			if (message === 128) {
				delete noteRegistrar[note];
			} else {
				noteRegistrar[note] = data;
			}
			if (onMidiEvent) {
				onMidiEvent(data);
			}
			//midi.currentTicks = currentTicks;
			///
			eventQueue.shift();
			///
			//test one bar 40px one best 10px
			// midi.moveCursor(currentTicks);
			///
			if (message === 128 && midi.currentTicks < midi.endTicks) {
				startAudioNew(currentTicks, true);
			}
		}, (currentTicks - offset) / midi.ticksPerBeat / midi.BPM * 60 * 1000);
	};

	var getContext = function () {
		if (MIDI.api === 'webaudio') {
			return MIDI.WebAudio.getContext();
		} else {
			midi.ctx = { currentTime: 0 };
		}
		return midi.ctx;
	};

	var getLength = function () {
		var data = midi.data;
		var length = data.length;
		var totalTime = 0.5;
		for (var n = 0; n < length; n++) {
			totalTime += data[n][1];
		}
		return totalTime;
	};
	var getLengthNew = function () {
		var data = midi.data;
		var totalTicks = 0;
		var length = data.length
		for (var n = 0; n < length; n++) {
			totalTicks += data[n][0].ticksToEvent;
		}
		return totalTicks;
	}
	var __now;
	var getNow = function () {
		if (window.performance && window.performance.now) {
			return window.performance.now();
		} else {
			return Date.now();
		}
	};

	var startAudio = function (currentTime, fromCache, onsuccess) {
		if (!midi.replayer) {
			return;
		}
		if (!fromCache) {
			if (typeof currentTime === 'undefined') {
				currentTime = midi.restart;
			}
			///
			midi.playing && stopAudio();
			midi.playing = true;
			midi.data = midi.replayer.getData();
			midi.endTime = getLength();
		}
		///
		var note;
		var offset = 0;
		var messages = 0;
		var data = midi.data;
		var ctx = getContext();
		var length = data.length;
		//
		queuedTime = 0.5;
		///
		var interval = eventQueue[0] && eventQueue[0].interval || 0;
		var foffset = currentTime - midi.currentTime;
		///
		if (MIDI.api !== 'webaudio') { // set currentTime on ctx
			var now = getNow();
			__now = __now || now;
			ctx.currentTime = (now - __now) / 1000;
		}
		///
		startTime = ctx.currentTime;
		///
		for (var n = 0; n < length && messages < 100; n++) {
			var obj = data[n];
			if ((queuedTime += obj[1]) <= currentTime) {
				offset = queuedTime;
				continue;
			}
			///
			currentTime = queuedTime - offset;
			///
			var event = obj[0].event;
			if (event.type !== 'channel') {
				continue;
			}
			///
			var channelId = event.channel;
			var channel = MIDI.channels[channelId];
			var delay = ctx.currentTime + ((currentTime + foffset + midi.startDelay) / 1000);
			var queueTime = queuedTime - offset + midi.startDelay;
			switch (event.subtype) {
				case 'controller':
					MIDI.setController(channelId, event.controllerType, event.value, delay);
					break;
				case 'programChange':
					MIDI.programChange(channelId, event.programNumber, delay);
					break;
				case 'pitchBend':
					MIDI.pitchBend(channelId, event.value, delay);
					break;
				case 'noteOn':
					if (channel.mute) break;
					note = event.noteNumber - (midi.MIDIOffset || 0);
					eventQueue.push({
						event: event,
						time: queueTime,
						source: MIDI.noteOn(channelId, event.noteNumber, event.velocity, delay),
						interval: scheduleTracking(channelId, note, queuedTime + midi.startDelay, offset - foffset, 144, event.velocity)
					});
					messages++;
					break;
				case 'noteOff':
					if (channel.mute) break;
					note = event.noteNumber - (midi.MIDIOffset || 0);
					eventQueue.push({
						event: event,
						time: queueTime,
						source: MIDI.noteOff(channelId, event.noteNumber, delay),
						interval: scheduleTracking(channelId, note, queuedTime, offset - foffset, 128, 0)
					});
					break;
				default:
					break;
			}
		}
		///
		onsuccess && onsuccess(eventQueue);
	};

	var startAudioNew = function (currentTicks, fromCache, onsuccess) {
		if (!midi.replayer) {
			return;
		}
		if (!fromCache) {
			if (typeof currentTicks === 'undefined') {
				currentTicks = midi.restart;
			}
			///
			midi.playing && stopAudio();
			midi.playing = true;
			midi.data = midi.replayer.getData();
			midi.endTicks = getLengthNew();
		}
		///
		//var onMessages = 0;
		var note;
		var offset = 0;
		var messages = 0;
		var data = midi.data;
		var ctx = getContext();
		var length = data.length;
		//
		// queuedTime = 0.5;
		queuedTicks = 0;

		if (MIDI.api !== 'webaudio') { // set currentTime on ctx
			var now = getNow();
			__now = __now || now;
			ctx.currentTime = (now - __now) / 1000;
		}
		do {
			midi.currentIndex++; //To play this note
			var obj = data[midi.currentIndex];
			offset = midi.currentTicks;
			midi.currentTicks += obj[0].ticksToEvent;
			var event = obj[0].event;
			if (event.type !== 'channel') {
				continue;
			}
			var channelId = event.channel;
			var channel = MIDI.channels[channelId];
			
			var delay = (midi.currentTicks - offset) / midi.ticksPerBeat / midi.BPM * 60;
			var queueTicks = midi.currentTicks;
			switch (event.subtype) {
				case 'controller':
					MIDI.setController(channelId, event.controllerType, event.value, delay);
					break;
				case 'programChange':
					MIDI.programChange(channelId, event.programNumber, delay);
					break;
				case 'pitchBend':
					MIDI.pitchBend(channelId, event.value, delay);
					break;
				case 'noteOn':
					delay = 0;
					if (channel.mute) break;
					note = event.noteNumber - (midi.MIDIOffset || 0);
					eventQueue.push({
						event: event,
						time: queueTicks,
						source: MIDI.noteOn(channelId, event.noteNumber, event.velocity, delay),
						interval: scheduleTracking(channelId, note, queueTicks, offset, 144, event.velocity, event)
					});
					// messages++;
					console.log("Enter NoteOn");
					break;
				case 'noteOff':
					if (channel.mute) break;
					note = event.noteNumber - (midi.MIDIOffset || 0);
					eventQueue.push({
						event: event,
						time: queueTicks,
						source: MIDI.noteOff(channelId, event.noteNumber, delay),
						interval: scheduleTracking(channelId, note, queueTicks, offset, 128, 0, event)
					});
					messages++;
					console.log("Enter Note Off", delay);
					break;
				default:
					break;
			}
		}while(midi.currentIndex < length-1 && !messages)
		///
		onsuccess && onsuccess(eventQueue);
	};
	var stopAudio = function () {
		var ctx = getContext();
		midi.playing = false;
		// midi.restart += (ctx.currentTime - startTime) * 1000;
		midi.restart = midi.currentTicks;
		// stop the audio, and intervals
		while (eventQueue.length) {
			var o = eventQueue.pop();
			window.clearInterval(o.interval);
			if (!o.source) continue; // is not webaudio
			if (typeof (o.source) === 'number') {
				window.clearTimeout(o.source);
			} else { // webaudio
				o.source.disconnect(0);
			}
		}
		// run callback to cancel any notes still playing
		for (var key in noteRegistrar) {
			var o = noteRegistrar[key]
			if (noteRegistrar[key].message === 144 && onMidiEvent) {
				onMidiEvent({
					channel: o.channel,
					note: o.note,
					now: o.now,
					end: o.end,
					message: 128,
					velocity: o.velocity
				});
			}
		}
		// reset noteRegistrar
		noteRegistrar = {};
	};

})();